#pragma once
#include "../math/matrix.h"
#include "../math/functions.h"

namespace NEngine {

// ================================================================================================
// Basic Camera class
// ================================================================================================
class CCamera {

    public:

    enum EHandy {
        eLeftHand,
        eRightHand,
    };

    private: 
        Matrix4x4 mat;              // our camera matrix
        Matrix4x4 viewmat;          // view space matrix
        float xrot;                 // x rotation
        float yrot;                 // y rotation
        Vector4 left;               // left
        Vector4 at;                 // camera look at
        Vector4 up;                 // up directions
        Vector4 pos;                // where the camera is in the world
        bool dirty;                 // do we need to create a new matrix
        EHandy handy;               // flag for handyness

        void ConstructMatrix();
    public:

        // -- default constructor
        CCamera();
        CCamera(const Vector4& at, const Vector4& up);
        CCamera(const Vector4& at, const Vector4& up, const Vector4& pos);
        CCamera(const Matrix4x4& mat);

        // -- mutator methods
        void SetHand(EHandy hand = eRightHand);
        void SetCamera(const Matrix4x4& mat);
        void SetCamera(const Vector4& at, const Vector4& up, const Vector4& pos);
        void SetPosition(const Vector4& pos_);
        void Translate(const Vector4& trans);
        void SetLookAt(const Vector4& at_);
        void SetUp(const Vector4&  up_);
        void RotateViewX(float rot);
        void RotateViewY(float rot);

        // -- accessor methods
        Vector4& GetPosition();
        Vector4& GetPosition() const;
        const Matrix4x4& GetViewTransform();
        const Matrix4x4& GetWorldTransform();
};

// ================================================================================================
// Constructor
// ================================================================================================
inline CCamera::CCamera() : left(1.0f, 0.0f, 0.0f), at(0.0f, 0.0f, 1.0f),
                          up(0.0f, 1.0f, 0.0f), pos(0.0f, 0.0f, 0.0f), 
                          dirty(false), xrot(0.0f), yrot(0.0f), handy(eRightHand) {
}

inline CCamera::CCamera(const Vector4& at_, const Vector4& up_) : left(), at(at_), up(up_),
                                                                pos(0.0f, 0.0f, 0.0f),
                                                                dirty(true), xrot(0.0f), 
                                                                yrot(0.0f) {
}

inline CCamera::CCamera(const Vector4& at_, const Vector4& up_, const Vector4& pos_) : left(),
                                                                                     at(at_),
                                                                                     up(up_),
                                                                                     pos(pos_),
                                                                                     dirty(true),
                                                                                     xrot(0.0f),
                                                                                     yrot(0.0f) {
}

inline CCamera::CCamera(const Matrix4x4& mat) : left(mat.GetRow1()), up(mat.GetRow2()), 
                                                at(mat.GetRow3()), pos(mat.GetRow4()), dirty(true),
                                                xrot(0.0f), yrot(0.0f) {
}

// ================================================================================================
// Accessor Methods
// ================================================================================================
inline const Matrix4x4& CCamera::GetViewTransform() {
    if(dirty)  {
        ConstructMatrix();
        dirty = false;
    }

    return viewmat;
}

inline const Matrix4x4& CCamera::GetWorldTransform() {
    if(dirty) {
        ConstructMatrix();
        dirty = false;
    }

    return mat;
}

inline Vector4& CCamera::GetPosition() {
    return pos;
}

inline void CCamera::SetHand(EHandy _handy) {
    handy = _handy;
}

// ================================================================================================
//  Mutator methods
// ================================================================================================
inline void CCamera::SetPosition(const Vector4& pos_) {
    pos = pos_;
    dirty = true;
}

inline void CCamera::SetLookAt(const Vector4& at_) {
    at = at_;
    dirty = true;
}

inline void CCamera::SetUp(const Vector4& up_) {
    up = up_;
    dirty = true;
}

inline void CCamera::Translate(const Vector4& trans) {
    pos += trans * mat.GetRotation();
    dirty = true;
}

inline void CCamera::RotateViewX(float rot) {
    xrot += rot;
    dirty = true;
}

inline void CCamera::RotateViewY(float rot) {
    yrot += rot;
    dirty = true;
}

} // NEngine namespace